package smallfrench.ui;

import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.event.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;

import javax.swing.AbstractAction;
import javax.swing.JOptionPane;
import javax.swing.JPopupMenu;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.KeyStroke;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingUtilities;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.UndoableEditEvent;
import javax.swing.event.UndoableEditListener;
import javax.swing.undo.CannotRedoException;
import javax.swing.undo.CannotUndoException;
import javax.swing.undo.UndoManager;
import javax.swing.undo.UndoableEdit;

/**
 * L’éditeur.
 * @author antoine1023
 *
 */
public final class Editeur {

	private final ActionDefaire actionDefaire;
	private final ActionRefaire actionRefaire;
	private final ActionRechercher actionRechercher;

	private final UndoManager undo = new UndoManager();

	private final FenetrePrincipale fenetrePrincipale;

	private final JPopupMenu popupMenu;
	private final JScrollPane scrollPane;
	private final MTextPane textPane;
	private final MDocument document;
	private final NumeroteurLignes numeroteurLignes;

	private File fichier;
	private EcouteurFichier ecouteurFichier;
	private boolean enregistre;

	/**
	 * Si <code>true</code>, toutes les éditions doivent être ignorées par le
	 * {@link UndoManager}.
	 */
	private boolean ignorerEditions;

	private float taillePolice = 14;

	//---------------------------------------------------------

	public Editeur(FenetrePrincipale fenetrePrincipale) {

		this.fenetrePrincipale = fenetrePrincipale;

		actionDefaire = new ActionDefaire();
		actionRefaire = new ActionRefaire();

		actionRechercher = new ActionRechercher();

		popupMenu = new JPopupMenu();
		popupMenu.add(actionDefaire);
		popupMenu.add(actionRefaire);
		popupMenu.addSeparator();
		popupMenu.add(Actions.COUPER);
		popupMenu.add(Actions.COPIER);
		popupMenu.add(Actions.COLLER);
		popupMenu.addSeparator();
		popupMenu.add(Actions.TOUT_SEL);

		document = new MDocument();

		document.addUndoableEditListener(new UndoableEditListener() {
			@Override
			public void undoableEditHappened(UndoableEditEvent e) {

				if (ignorerEditions)
					return;

				UndoableEdit edit = e.getEdit();
				String nom = edit.getPresentationName().toLowerCase();

				if (nom.contains("style")) { 
					// UGLY HACK
					// TODO Je crois que pas mal de choses sont à réécrire...
					return;
				}

				undo.addEdit(e.getEdit());
				actionDefaire.updateUndoState();
				actionRefaire.updateRedoState();
			}
		});

		textPane = new MTextPane(document);
		miseAJourPolice();
		textPane.addMouseListener(new MouseListener() {
			@Override
			public void mouseReleased(MouseEvent e) {}

			@Override
			public void mousePressed(MouseEvent e) {}

			@Override
			public void mouseExited(MouseEvent e) {}

			@Override
			public void mouseEntered(MouseEvent e) {}

			@Override
			public void mouseClicked(MouseEvent e) {
				if (e.getClickCount() == 1 &&
						e.getButton() == MouseEvent.BUTTON3)
					affMenuPopup(e.getComponent(), e.getX(), e.getY());
			}
		});

		document.addDocumentListener(new DocumentListener() {

			@Override
			public void removeUpdate(DocumentEvent arg0) {
				if (enregistre) {
					enregistre = false;
					if (ecouteurFichier != null)
						ecouteurFichier.nonEnregistre();
				}
			}

			@Override
			public void insertUpdate(DocumentEvent arg0) {
				if (enregistre) {
					enregistre = false;
					if (ecouteurFichier != null)
						ecouteurFichier.nonEnregistre();
				}
			}

			@Override
			public void changedUpdate(DocumentEvent arg0) {
			}
		});

		numeroteurLignes = new NumeroteurLignes(this);

		scrollPane = new JScrollPane(textPane,
				ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS,
				ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);

		scrollPane.setBackground(Color.WHITE);
		scrollPane.getViewport().setBackground(Color.WHITE);

		scrollPane.addMouseWheelListener(new MouseWheelListener() {

			@Override
			public void mouseWheelMoved(MouseWheelEvent e) {
				if (e.isControlDown()) {
					int unites = e.getUnitsToScroll();
					if (unites > 0) {
						if (taillePolice > 8f)
							taillePolice *= 0.9;
					} else {
						if (taillePolice < 100f)
							taillePolice *= 1.1;
					}
					miseAJourPolice();
				}
			}
		});

		textPane.setViewport(scrollPane.getViewport());

		scrollPane.getVerticalScrollBar().addAdjustmentListener(
				new AdjustmentListener() {

					@Override
					public void adjustmentValueChanged(AdjustmentEvent arg0) {
						numeroteurLignes.rafraichir();
					}
				});

		scrollPane.setRowHeaderView(numeroteurLignes);

		enregistre = true;
		ignorerEditions = false;
	}

	private void miseAJourPolice() {
		if (scrollPane != null)
			scrollPane.getVerticalScrollBar().setValue(0);

		Font police = Ressources.policeMonospaceJRE
				.deriveFont(taillePolice);

		// On utilise popupMenu mais ça marche avec n’importe quel composant ;
		// puisque n’importe quel composant possède une méthode getFontMetrics
		FontMetrics fm = popupMenu.getFontMetrics(police);
		document.setPolice(fm.charWidth(' '));

		textPane.setFont(police);

		if (numeroteurLignes != null)
			numeroteurLignes.rafraichir();
	}

	//---------------------------------------------------------

	private void affMenuPopup(Component c, int x, int y) {
		popupMenu.show(c, x, y);
	}

	public void ouvrir(String nomFichier) {
		ouvrir(new File(nomFichier));
	}

	/**
	 * Édite le script.
	 * @param fichier Le script.
	 */
	public void ouvrir(File fichier) {

		try {
			FileInputStream fis = new FileInputStream(fichier);
			String s = Ressources.lireFluxUTF8(fis);

			textPane.setText(s);

			defilementHaut();
			undo.discardAllEdits();

			this.fichier = fichier;

			enregistre = true;
			if (ecouteurFichier != null)
				ecouteurFichier.enregistre();

		} catch (IOException e) {
			e.printStackTrace();
			JOptionPane.showMessageDialog(fenetrePrincipale, "Impossible "
					+ "d’ouvrir ce fichier, une erreur s’est produite",
					"Erreur", JOptionPane.ERROR_MESSAGE);
		}

	}

	/**
	 * Édite le script.
	 * @param script Le script.
	 */
	public void ouvrirFichierExemple(String script) {
		ignorerEditions = true;
		textPane.setText(script);
		defilementHaut();
		ignorerEditions = false;

		fichier = null;

		enregistre = true;
		if (ecouteurFichier != null)
			ecouteurFichier.nonEnregistre();
	}

	private void defilementHaut() {
		SwingUtilities.invokeLater(new Runnable() {
			@Override
			public void run() {
				JScrollBar vsb = scrollPane.getVerticalScrollBar();
				vsb.setValue(vsb.getMinimum());
			}
		});
	}

	/**
	 * Lors de Ctrl + S
	 */
	public void enregistrer() {
		enregistrer(fichier);
	}

	/**
	 * Enregistrer sous...
	 * @param fichier
	 */
	public void enregistrer(File fichier) {
		try {
			FileOutputStream fos = new FileOutputStream(fichier);
			OutputStreamWriter w = new OutputStreamWriter(fos, "UTF-8");
			w.write(getTexte());
			w.close();
			System.out.println("Fichier enregistré.");

			this.fichier = fichier;

			enregistre = true;
			if (ecouteurFichier != null)
				ecouteurFichier.enregistre();

		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * 
	 * @return true si un enregistrement est possible
	 */
	public boolean enregistrable() {
		return fichier != null;
	}

	public String getTexte() {
		return textPane.getText();
	}

	public ActionDefaire getActionDefaire() {
		return actionDefaire;
	}

	public ActionRefaire getActionRefaire() {
		return actionRefaire;
	}

	public ActionRechercher getActionRechercher() {
		return actionRechercher;
	}

	public JScrollPane getScrollPane() {
		return scrollPane;
	}

	public MTextPane getTextPane() {
		return textPane;
	}

	public boolean estEnregistre() {
		return enregistre;
	}

	public EcouteurFichier getEcouteurFichier() {
		return ecouteurFichier;
	}

	public void setEcouteurFichier(EcouteurFichier ecouteurFichier) {
		this.ecouteurFichier = ecouteurFichier;
	}

	public File getFichier() {
		return fichier;
	}

	public void setColorationLexicale(boolean activee) {
		document.setColorationActivee(activee);
	}

	public boolean getColorationLexicale() {
		return document.getColorationActivee();
	}

	//---------------------------------------------------------

	@SuppressWarnings("serial")
	class ActionDefaire extends AbstractAction {
		public ActionDefaire() {
			super("Annuler", Ressources.getIcone("edit-undo"));
			putValue(SHORT_DESCRIPTION, "Annuler la dernière modification");
			putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke("control Z"));
			setEnabled(false);
		}

		@Override
		public void actionPerformed(ActionEvent e) {
			try {
				undo.undo();
			} catch (CannotUndoException ex) {
				System.out.println("Impossible de d’annuler : " + ex);
				ex.printStackTrace();
			}
			updateUndoState();
			actionRefaire.updateRedoState();
		}

		protected void updateUndoState() {
			if (undo.canUndo()) {
				setEnabled(true);
			} else {
				setEnabled(false);
			}
		}
	}

	@SuppressWarnings("serial")
	class ActionRefaire extends AbstractAction {
		public ActionRefaire() {
			super("Rétablir", Ressources.getIcone("edit-redo"));
			putValue(SHORT_DESCRIPTION, "Rétablir la dernière modification");
			putValue(ACCELERATOR_KEY,
					KeyStroke.getKeyStroke("shift control Z"));
			setEnabled(false);
		}

		@Override
		public void actionPerformed(ActionEvent e) {
			try {
				undo.redo();
			} catch (CannotRedoException ex) {
				System.out.println("Impossible de rétablir : " + ex);
				ex.printStackTrace();
			}
			updateRedoState();
			actionDefaire.updateUndoState();
		}

		protected void updateRedoState() {
			if (undo.canRedo()) {
				setEnabled(true);
			} else {
				setEnabled(false);
			}
		}
	}


	@SuppressWarnings("serial")
	public class ActionRechercher extends AbstractAction {
		public ActionRechercher() {
			super("Rechercher et remplacer",
					Ressources.getIcone("edit-find-replace"));
			putValue(SHORT_DESCRIPTION, "Rechercher et remplacer " +
					"automatiquement des parties du fichier");
			putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke("control F"));
		}
		@Override
		public void actionPerformed(ActionEvent e) {
			DialogueRechercher d = fenetrePrincipale.getDialogueRechercher();
			d.setTextPane(textPane);
			d.setVisible(true);
		}
	}
}
